﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Threading;
using Mesuro.Common;
using Mesuro.Graphing.Utilities;
using System.Windows.Input;
using System.Windows.Shapes;
using System.ComponentModel;

namespace Mesuro.Graphing {
	[AxisProperty( "ShowPrimaryAxes", Label = "Primary Axes", AsCheckBox = true, CheckboxThreeState = true )]
	[AxisProperty( "ShowSecondaryAxes", Label = "Secondary Axes", AsCheckBox = true, CheckboxThreeState = true )]
	public class CartesianAxes : MultiAxes {
		public const string Key = "Cartesian Plot";
		public const int AXES_Primary = 0;
		public const int AXES_Secondary = 1;

		private ScaleCollection<VisibleScale>[] scales = {
		/* Primary Scales: */	new ScaleCollection<VisibleScale>( new VisibleScale(), new VisibleScale(){Invert=true} ),
		/* Secondary Scales: */	new ScaleCollection<VisibleScale>( new VisibleScale(){Flags=VisibleScaleFlags.LockNumTicks|VisibleScaleFlags.ShowAllData}, new VisibleScale(){Invert=true,Flags=VisibleScaleFlags.LockNumTicks|VisibleScaleFlags.ShowAllData} )
														 };

		public new RectangleGeometry PlotArea {
			get { return (RectangleGeometry)base.PlotArea; }
			set { base.PlotArea = value; }
		}

		private int _secondaryDataPlotted = 0;

		public bool SecondaryDataPlotted {
			get { return _secondaryDataPlotted > 0; }
		}

		#region DP: ShowPrimaryAxes
		public static readonly DependencyProperty ShowPrimaryAxesProperty = DependencyProperty.RegisterAttached( "ShowPrimaryAxes", typeof( bool? ), typeof( CartesianAxes ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ShowPrimaryAxesChangedStatic ) );
		public bool? ShowPrimaryAxes {
			get { return (bool?)GetValue( ShowPrimaryAxesProperty ); }
			set { SetValue( ShowPrimaryAxesProperty, value ); }
		}
		public static bool? GetShowPrimaryAxes( DependencyObject element ) { return (bool?)element.GetValue( ShowPrimaryAxesProperty ); }
		public static void SetShowPrimaryAxes( DependencyObject element, bool? value ) { element.SetValue( ShowPrimaryAxesProperty, value ); }
		private static void ShowPrimaryAxesChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: ShowSecondaryAxes
		public static readonly DependencyProperty ShowSecondaryAxesProperty = DependencyProperty.RegisterAttached( "ShowSecondaryAxes", typeof( bool? ), typeof( CartesianAxes ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ShowSecondaryAxesChangedStatic ) );
		public bool? ShowSecondaryAxes {
			get { return (bool?)GetValue( ShowSecondaryAxesProperty ); }
			set { SetValue( ShowSecondaryAxesProperty, value ); }
		}
		public static bool? GetShowSecondaryAxes( DependencyObject element ) { return (bool?)element.GetValue( ShowSecondaryAxesProperty ); }
		public static void SetShowSecondaryAxes( DependencyObject element, bool? value ) { element.SetValue( ShowSecondaryAxesProperty, value ); }
		private static void ShowSecondaryAxesChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		public bool ShowingPrimaryAxes {
			get { return ShowPrimaryAxes ?? true; }
		}
		public bool ShowingSecondaryAxes {
			get { return ShowSecondaryAxes ?? SecondaryDataPlotted; }
		}

		private Rectangle _selection = new Rectangle();
		protected Visual SelectionShape {
			get { return _selection; }
		}
		public Rectangle Selection {
			get {
				return _selection;
			}
		}

		static CartesianAxes() {
			MultiAxes.SetAxisSetDefinitionsForType( typeof( CartesianAxes ),
				new AxisSetDefinition( "Primary",
					new AxisDefinition( "Horizontal Axis", "X Axis" ),
					new AxisDefinition( "Vertical Axis", "Y Axis" ) ),
				new AxisSetDefinition( "Secondary",
					new AxisDefinition( "Horizontal Axis", "X2 Axis" ),
					new AxisDefinition( "Vertical Axis", "Y2 Axis" ) )
			);
		}
		public CartesianAxes()
			: base( 2 ) {
			PlotArea = new RectangleGeometry();
			scales[0][0].PropertyChanged += OnScalePropertyChanged;
			scales[0][1].PropertyChanged += OnScalePropertyChanged;
			scales[1][0].PropertyChanged += OnScalePropertyChanged;
			scales[1][1].PropertyChanged += OnScalePropertyChanged;
		}

		protected override void OnAddPlot( Plot plot ) {
			base.OnAddPlot( plot );
			int axisSet = GetAxisSet( plot ) % 2;
			ScaleCollection<VisibleScale> scales = this.scales[axisSet];
			scales[0].Range = Limits.Union( scales[0].Range, plot.Limits[0] );
			scales[1].Range = Limits.Union( scales[1].Range, plot.Limits[1] );

			if (axisSet == AXES_Secondary)
				_secondaryDataPlotted++;
		}
		protected override void OnPlotDataChanged( object sender, NotifySeriesChangedEventArgs e ) {
			base.OnPlotDataChanged( sender, e );

			if (sender is Plot) {
				int axisSet = GetAxisSet( (Plot)sender );
				ScaleCollection<VisibleScale> scales = this.scales[axisSet];
				scales[0].Range = new Limits();
				scales[1].Range = new Limits();

				foreach (Plot plot in Plots) {
					if (axisSet == GetAxisSet( plot )) {
						scales[0].Range = Limits.Union( scales[0].Range, plot.Limits[0] );
						scales[1].Range = Limits.Union( scales[1].Range, plot.Limits[1] );
					}
				}
			}
		}

		protected override void OnAxisSetChanged( Plot plot, int oldAxisSet, int newAxisSet ) {
			scales[0][0].Range = new Limits();
			scales[0][1].Range = new Limits();
			scales[1][0].Range = new Limits();
			scales[1][1].Range = new Limits();

			if (oldAxisSet == AXES_Secondary) _secondaryDataPlotted--;
			if (newAxisSet == AXES_Secondary) _secondaryDataPlotted++;

			foreach (Plot p in Plots) {
				int axisSet = GetAxisSet( p );
				scales[axisSet][0].Range = Limits.Union( scales[axisSet][0].Range, p.Limits[0] );
				scales[axisSet][1].Range = Limits.Union( scales[axisSet][1].Range, p.Limits[1] );
			}

			InvalidateVisual();
			base.OnAxisSetChanged( plot, oldAxisSet, newAxisSet );
		}
		protected override void OnAxisDefinitionChanged( object sender, PropertyChangedEventArgs e ) {
			base.OnAxisDefinitionChanged( sender, e );
			InvalidateVisual();
		}

		protected override Size MeasureOverride( Size availableSize ) {
			Limits[,] dataLimits = new Limits[2, 2];

			//Set the scales
			foreach (Plot plot in Plots) {
				int axisSet = GetAxisSet( plot ) % 2;

				ScaleCollection<VisibleScale> axes = this.scales[axisSet];

				Limits xl = plot.Limits[0], yl = plot.Limits[1];

				dataLimits[axisSet, 0].Union( xl );
				dataLimits[axisSet, 1].Union( yl );
			}

			scales[0][0].Range = dataLimits[0, 0];
			scales[0][1].Range = dataLimits[0, 1];
			scales[1][0].Range = dataLimits[1, 0];
			scales[1][1].Range = dataLimits[1, 1];

			return base.MeasureOverride( availableSize );
		}
		protected override Size ArrangeOverride( Size graphSize ) {
			if (Graph.GetIsIcon( this )) {
				scales[0][0].Flags |= VisibleScaleFlags.LockNumTicks;
				scales[0][1].Flags |= VisibleScaleFlags.LockNumTicks;
				scales[0][0].NumTicks = 2;
				scales[0][1].NumTicks = 2;
			} else {
				scales[0][0].NumTicks = (int)(graphSize.Width / MinimumTickSpacing + 0.5);
				scales[1][0].NumTicks = scales[0][0].NumTicks;
				scales[0][1].NumTicks = (int)(graphSize.Height / MinimumTickSpacing + 0.5);
				scales[1][1].NumTicks = scales[0][1].NumTicks;
			}
			PlotArea.Rect = new Rect( graphSize );
			if (ShowLabels)
				PlotArea.Rect -= new Insets(
					primaryTextClearance + 3 * TextPadding * 2 + ScaleTextSize,
					2 * ScaleTextSize + 3 * TextPadding,
					secondaryTextClearance + 3 * TextPadding * 2 + ScaleTextSize,
					2 * ScaleTextSize + 3 * TextPadding );

			scales[0][0].Domain = scales[1][0].Domain = new Limits( PlotArea.Rect.Left, PlotArea.Rect.Right );
			scales[0][1].Domain = scales[1][1].Domain = new Limits( PlotArea.Rect.Top, PlotArea.Rect.Bottom );

			TransformGroup[] tg = { new TransformGroup(), new TransformGroup() };
			if (scales[0][0].Invert) tg[0].Children.Add( new ScaleTransform( -1, 1 ) );
			if (scales[0][1].Invert) tg[0].Children.Add( new ScaleTransform( 1, -1 ) );
			if (scales[1][0].Invert) tg[1].Children.Add( new ScaleTransform( -1, 1 ) );
			if (scales[1][1].Invert) tg[1].Children.Add( new ScaleTransform( 1, -1 ) );

			foreach (Plot plot in Plots) {

				Rect par = PlotArea.Rect;
				int axisSet = GetAxisSet( plot ) % 2;

				ScaleCollection<VisibleScale> axes = this.scales[axisSet];

				Limits xl = plot.Limits[0], yl = plot.Limits[1];

				double lt, rt, tp, bm;
				lt = axes[0].ToPlotSpace( xl.Min );
				rt = axes[0].ToPlotSpace( xl.Max );
				bm = axes[1].ToPlotSpace( yl.Min );
				tp = axes[1].ToPlotSpace( yl.Max );
				plot.Arrange( new Rect( lt, tp, rt - lt, bm - tp ) );

				plot.RenderTransform = tg[axisSet];
				plot.RenderTransformOrigin = new Point( 0.5, 0.5 );
			}

			return graphSize;
		}

		protected virtual void OnScalePropertyChanged( object sender, PropertyChangedEventArgs e ) {
			switch (e.PropertyName) {
				case VisibleScale.PROP_Ticks:
					_measureTickText(); break;
				case VisibleScale.PROP_VisibleRange:
					InvalidateVisual();
					break;
				case VisibleScale.PROP_Invert:
					InvalidateArrange();
					break;
			}
		}

		private double primaryTextClearance;
		private double secondaryTextClearance;
		private void _measureTickText() {
			primaryTextClearance = secondaryTextClearance = 0;
			VisibleScale scale = scales[0][1];
			for (int i = 0; i < scale.NumTicks; i++) {
				Size sz; double plot;
				decimal val = scale.GetTick( i, out plot );
				CreateGlyphRun( val.ToString(), ScaleTextSize, Typeface, out sz );
				primaryTextClearance = Math.Max( primaryTextClearance, sz.Width );
			}
			scale = scales[1][1];
			for (int i = 0; i < scale.NumTicks; i++) {
				Size sz; double plot;
				decimal val = scale.GetTick( i, out plot );
				CreateGlyphRun( val.ToString(), ScaleTextSize, Typeface, out sz );
				secondaryTextClearance = Math.Max( primaryTextClearance, sz.Width );
			}
		}

		protected override void OnRender( DrawingContext cx ) {
			base.OnRender( cx );
			cx.DrawGeometry( PlotBackground, null, PlotArea );
			if (ShowingPrimaryAxes) {
				#region Primary X
				VisibleScale xAxis = scales[0][0];
				{
					//Draw Gridlines
					double plotVal;
					int minRat = Graph.GetIsIcon( this ) ? 1 : xAxis.GetSubdivisions( 5 );
					for (double i = 0; i <= minRat * xAxis.NumTicks; i++) {
						decimal val = xAxis.GetTick( i / minRat, out plotVal );
						Point p1 = new Point( plotVal, PlotArea.Rect.Bottom );
						Point p2 = new Point( plotVal, PlotArea.Rect.Top );
						if (i % minRat != 0) {
							cx.DrawLine( MinorGridlines, p1, p2 );
							continue;
						}
						cx.DrawLine( MajorGridlines, p1, p2 );
						if (ShowLabels) {
							Size sz = new Size();
							GlyphRun txt = CreateGlyphRun( val.ToString(), ScaleTextSize, Typeface, out sz );
							cx.PushTransform( new TranslateTransform( (int)(p1.X - sz.Width / 2 + .5), TextPadding + (int)(p1.Y + sz.Height + .5) ) );
							cx.DrawGlyphRun( ScaleTextBrush, txt );
							cx.Pop();
						}
					}
					//Draw Label
					if (ShowLabels) {
						Size szLabel = new Size();
						GlyphRun txtLabel = CreateGlyphRun( AxisSetDefinitions[0][0].AxisLabel, ScaleTextSize, Typeface, out szLabel );
						cx.PushTransform( new TranslateTransform( (int)((PlotArea.Rect.Left + PlotArea.Rect.Right - szLabel.Width) / 2 + .5), 2 * TextPadding + (int)(PlotArea.Rect.Bottom + ScaleTextSize + szLabel.Height + .5) ) );
						cx.DrawGlyphRun( ScaleTextBrush, txtLabel );
						cx.Pop();
					}
				}
				#endregion
				#region Primary Y
				VisibleScale yAxis = scales[0][1];
				{
					double plotVal;
					int minRat = Graph.GetIsIcon( this ) ? 1 : xAxis.GetSubdivisions( 5 ); 
					for (double i = 0; i <= minRat * yAxis.NumTicks; i++) {
						decimal val = yAxis.GetTick( i / minRat, out plotVal );
						Point p1 = new Point( PlotArea.Rect.Left, plotVal );
						Point p2 = new Point( PlotArea.Rect.Right, plotVal );
						if (i % minRat != 0) {
							cx.DrawLine( MinorGridlines, p1, p2 );
							continue;
						}
						cx.DrawLine( MajorGridlines, p1, p2 );
						if (ShowLabels) {
							Size sz = new Size();
							GlyphRun txt = CreateGlyphRun( val.ToString(), ScaleTextSize, Typeface, out sz );
							cx.PushTransform( new TranslateTransform( -TextPadding + (int)(p1.X - sz.Width + .5), (int)(p1.Y + sz.Height / 2 + .5) ) );
							cx.DrawGlyphRun( ScaleTextBrush, txt );
							cx.Pop();
						}
					}
					//Draw Label
					if (ShowLabels) {
						Size szLabel = new Size();
						GlyphRun txtLabel = CreateGlyphRun( AxisSetDefinitions[0][1].AxisLabel, ScaleTextSize, Typeface, out szLabel );
						cx.PushTransform( new TranslateTransform( (int)(PlotArea.Rect.Left - primaryTextClearance - 3 * TextPadding), (int)((PlotArea.Rect.Bottom + PlotArea.Rect.Top + szLabel.Width) / 2 + .5) ) );
						cx.PushTransform( new RotateTransform( -90 ) );
						cx.DrawGlyphRun( ScaleTextBrush, txtLabel );
						cx.Pop();
						cx.Pop();
					}
				}
				#endregion
				if (yAxis.VisibleRange.Contains( 0 ))
					cx.DrawLine( ScalePen, new Point( PlotArea.Rect.Left, yAxis.ToPlotSpace( 0 ) ), new Point( PlotArea.Rect.Right, yAxis.ToPlotSpace( 0 ) ) );
				if (xAxis.VisibleRange.Contains( 0 ))
					cx.DrawLine( ScalePen, new Point( xAxis.ToPlotSpace( 0 ), PlotArea.Rect.Top ), new Point( xAxis.ToPlotSpace( 0 ), PlotArea.Rect.Bottom ) );
			}
			if (ShowingSecondaryAxes) {
				#region Secondary X
				VisibleScale xAxis = scales[1][0];
				{
					double plotVal;
					for (double i = 0; i <= xAxis.NumTicks; i += 1) {
						decimal val = xAxis.GetTick( i, out plotVal );
						Point p2 = new Point( plotVal, PlotArea.Rect.Top );
						if (ShowLabels) {
							Size sz = new Size();
							GlyphRun txt = CreateGlyphRun( val.ToString(), ScaleTextSize, Typeface, out sz );
							cx.PushTransform( new TranslateTransform( (int)(p2.X - sz.Width / 2 + .5), -2 * TextPadding + (int)(p2.Y + .5) ) );
							cx.DrawGlyphRun( ScaleTextBrush, txt );
							cx.Pop();
						}
					}
					//Draw Label
					if (ShowLabels) {
						Size szLabel = new Size();
						GlyphRun txtLabel = CreateGlyphRun( AxisSetDefinitions[1][0].AxisLabel, ScaleTextSize, Typeface, out szLabel );
						cx.PushTransform( new TranslateTransform( (int)((PlotArea.Rect.Left + PlotArea.Rect.Right - szLabel.Width) / 2 + .5), -2 * TextPadding + (int)(PlotArea.Rect.Top - ScaleTextSize + .5) ) );
						cx.DrawGlyphRun( ScaleTextBrush, txtLabel );
						cx.Pop();
					}
				}
				#endregion
				#region Secondary Y
				VisibleScale yAxis = scales[1][1];
				{
					double plotVal;
					double space = 0;
					for (double i = 0; i <= yAxis.NumTicks; i += 1) {
						decimal val = yAxis.GetTick( i, out plotVal );
						Point p2 = new Point( PlotArea.Rect.Right, plotVal );
						Size sz = new Size();
						GlyphRun txt = CreateGlyphRun( val.ToString(), ScaleTextSize, Typeface, out sz );
						space = Math.Max( space, sz.Width );
						cx.PushTransform( new TranslateTransform( TextPadding + (int)(p2.X + .5), (int)(p2.Y + sz.Height / 2 + .5) ) );
						cx.DrawGlyphRun( ScaleTextBrush, txt );
						cx.Pop();
					}
					//Draw Label
					if (ShowLabels) {
						Size szLabel = new Size();
						GlyphRun txtLabel = CreateGlyphRun( AxisSetDefinitions[1][1].AxisLabel, ScaleTextSize, Typeface, out szLabel );
						cx.PushTransform( new TranslateTransform( 2 * TextPadding + (int)(PlotArea.Rect.Right + space + szLabel.Height + .5), (int)((PlotArea.Rect.Bottom + PlotArea.Rect.Top + szLabel.Width) / 2 + .5) ) );
						cx.PushTransform( new RotateTransform( -90 ) );
						cx.DrawGlyphRun( ScaleTextBrush, txtLabel );
						cx.Pop();
						cx.Pop();
					}
				}
				#endregion
				if (yAxis.VisibleRange.Contains( 0 ))
					cx.DrawLine( ScalePen, new Point( PlotArea.Rect.Left, yAxis.ToPlotSpace( 0 ) ), new Point( PlotArea.Rect.Right, yAxis.ToPlotSpace( 0 ) ) );
				if (xAxis.VisibleRange.Contains( 0 ))
					cx.DrawLine( ScalePen, new Point( xAxis.ToPlotSpace( 0 ), PlotArea.Rect.Top ), new Point( xAxis.ToPlotSpace( 0 ), PlotArea.Rect.Bottom ) );
			}
			cx.DrawGeometry( null, ScalePen, PlotArea );
		}

		protected Common.Vector ToDataSpace( Point coord ) {
			Common.Vector ret = new Common.Vector( 2 );
			ret[0] = scales[0][0].ToDataSpace( coord.X );
			ret[1] = scales[0][1].ToDataSpace( coord.Y );
			return ret;
		}

		protected Point ToPlotSpace( Common.Vector datum ) {
			return new Point( scales[0][0].ToPlotSpace( datum[0] ), scales[0][1].ToPlotSpace( datum[1] ) );
		}

		protected override void OnRenderSizeChanged( SizeChangedInfo sizeInfo ) {
			base.OnRenderSizeChanged( sizeInfo );
			InvalidateVisual();
		}

	}

}
